package RSA;

import NumTheory.NumTheory;

import java.math.BigInteger;
import java.util.Random;

public class RSA {

    private static final Random RANDOM = new Random();
    private static final NumTheory NUM_THEORY = new NumTheory();

    /**
     * 加密函数
     *
     * @param plaintext 待加密的明文，其实是一个整数
     * @param e         公钥是（e,n）
     * @param n         公钥是（e,n）
     * @return 返回加密后的密文，也是一个整数
     */
    public BigInteger encrypt(BigInteger plaintext, BigInteger e, BigInteger n) {
        // fastPow为快速模幂算法
        return NUM_THEORY.fastPow(plaintext, e, n);
    }

    /**
     * 解密函数
     *
     * @param ciphertext 待加密的密文，其实是一个整数
     * @param d          私钥是（d,n）
     * @param n          私钥是（d,n）
     * @return 返回解密后的明文，也是一个整数
     */
    public BigInteger decrypt(BigInteger ciphertext, BigInteger d, BigInteger n) {
        return NUM_THEORY.fastPow(ciphertext, d, n);
    }

    /**
     * 采用MR素性检测算法来检测一个数是否是素数，因为是每次检测有0.25的失败率，通过增多次数来提高概率，
     * 检测10次，那么成功的概率为0.99999904632568359375
     *
     * @param target 待检测是否为素数的数
     * @return 返回素数检测的结果： 是 / 否
     */
    private boolean isPrime(BigInteger target) {
        return NUM_THEORY.MRtest(target, 10);
    }

    private BigInteger getPrime(int bitCount) {
        BigInteger primeNum = new BigInteger(bitCount, RANDOM);
        // 若 primeNum 为偶数，则将其加 1 ，使其变成奇数
        if (primeNum.mod(BigInteger.valueOf(2)).equals(BigInteger.ZERO)) {
            primeNum = primeNum.add(BigInteger.ONE);
        }
        // isPrime是素数检测算法，可以采用Miller-Rabin素性检测算法，
        // 这里是个概率问题，要注意到失败的概率
        while (!isPrime(primeNum)) {
            primeNum = primeNum.add(BigInteger.valueOf(2));
        }
        return primeNum;
    }

    /**
     * 用于生成加解密所需的公钥和私钥，公钥是（e,n），私钥是（d,n）
     *
     * @param bits 安全参数，比特位数
     * @return 返回{e, d, n}
     */
    public BigInteger[] generateKey(int bits) {
        int sizeP = bits / 2;
        int sizeQ = bits - sizeP;
        BigInteger p = getPrime(sizeP);
        BigInteger q = getPrime(sizeQ);
        if (p == null || q == null) {
            throw new RuntimeException("大素数p或q生成失败!");
        }

        BigInteger n = p.multiply(q);
        BigInteger phiN = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));

        // 选择整数e，使得gcd(phiN,e)==1，且e>1
        BigInteger e;
        do {
            e = new BigInteger(bits, RANDOM);
        } while (!e.gcd(phiN).equals(BigInteger.ONE));

        // 计算d=e^(-1) mod phiN
        BigInteger d = e.modInverse(phiN);

        return new BigInteger[]{e, d, n};
    }

}
